package com.effective.java.generics;

import java.util.Arrays;
import java.util.EmptyStackException;

/**
 * Class representing a stack (last in, first out data structure) containing
 * objects of an arbitrary type.
 * 
 * @author andreas.landerer
 * 
 * @param <E>
 *            any type
 */
public final class Stack<E> {

	/**
	 * Default initial capacity of the stack
	 */
	private static final int DEFAULT_INITIAL_CAPACITY = 16;

	/**
	 * Elements of the stack
	 */
	private E[] elements;

	/**
	 * Size of the stack
	 */
	private int size;

	/**
	 * Constructor creating a new instance of the type {@link Stack}
	 */
	@SuppressWarnings("unchecked")
	public Stack() {

		// This array will merely contain elements of type E. This is ensured by
		// the method push which is the single source of adding new elements to
		// the stack. Therefore the cast to E[] is safe.
		this.elements = (E[]) new Object[DEFAULT_INITIAL_CAPACITY];
		// this won't compile since generics are non-reifiable
		// this.elements = new E[DEFAULT_INITIAL_CAPACITY];
	}

	/**
	 * Method pushing a new element onto the stack
	 * 
	 * @param element
	 *            new element
	 */
	public void push(E element) {

		this.ensureCapacity();
		this.elements[this.size++] = element;
	}

	/**
	 * Method returning the latest element from the stack
	 * 
	 * @return latest element
	 */
	public E pop() {

		if (this.size == 0) {
			throw new EmptyStackException();
		}

		E result = this.elements[--this.size];
		this.elements[this.size] = null;
		return result;
	}

	/**
	 * Method ensuring a proper capacity of the stack
	 */
	private void ensureCapacity() {

		if (this.elements.length == this.size) {
			elements = Arrays.copyOf(this.elements, 2 * this.size + 1);
		}
	}

	/**
	 * Method transforming a stack instance into a string sequence containing
	 * its elements. E.g. [elementA, elementB, ...]
	 * 
	 * @return string sequence representing a stack instance
	 */
	@Override
	public String toString() {

		StringBuilder builder = new StringBuilder();
		builder.append('[');
		for (int i = 0; i < this.elements.length; i++) {
			E element = this.elements[i];
			if (element != null) {
				if (i > 0) {
					builder.append(", ");
				}
				builder.append(element);
			}
		}
		builder.append(']');
		return builder.toString();
	}
}
